package com.api.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;

import org.testng.Assert;

public final class AssertUtil {

	private AssertUtil() {
	}

	public static <T> void assertDTOEquals(T actual, T expected) {

		assertDTOEquals(actual, expected, "Expected");
	}

	public static <T> void assertCollectionDTOEquals(Collection<T> actual, Collection<T> expected) {
		assertCollectionDTOEquals(actual, expected, "Expected");
	}

	public static <T> void assertCollectionDTOEquals(Collection<T> actual, Collection<T> expected, String message) {

		if (actual == null || expected == null) {

			throw new IllegalArgumentException("Actual and expected collections cannot be null");
		}
		Assert.assertEquals(actual.size(), expected.size(), "Collection size");

		Iterator<T> actualIt = actual.iterator();
		Iterator<T> expectedIt = expected.iterator();

		while (actualIt.hasNext()) {

			T actualValue = actualIt.next();
			T expectedValue = expectedIt.next();

			assertDTOEquals(actualValue, expectedValue, message);
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T> void assertDTOEquals(T actual, T expected, String message) {

		if (actual == null && expected != null || actual != null && expected == null) {

			throw new AssertionError(String.format("%s values aren't equal - expected is '%s' but actual was '%s'", message, expected,
					actual));
		}

		if (actual != null && expected != null) {

			if (isSimpleValue(actual)) {

				compareSimpleValues(actual, expected, message);

			} else if (Collection.class.isAssignableFrom(actual.getClass())) {

				assertCollectionDTOEquals((Collection) actual, (Collection) expected, message);

			} else {

				compareDto(actual, expected, message);
			}
		}
	}

	private static boolean isSimpleValue(Object object) {
		Class<?> objClass = object.getClass();
		return (String.class.isAssignableFrom(objClass) || Integer.class.isAssignableFrom(objClass)
				|| Long.class.isAssignableFrom(objClass) || Boolean.class.isAssignableFrom(objClass));
	}

	private static void compareSimpleValues(Object actual, Object expected, String message) {
		if (!actual.equals(expected)) {
			throw new AssertionError(String.format("%s values aren't equal - expected is '%s' but actual was '%s'", message, expected,
					actual));
		}
	}

	private static void compareDto(Object actual, Object expected, String message) {
		Class<?> dtoClass = actual.getClass();
		Field[] fields = dtoClass.getDeclaredFields();

		for (Field field : fields) {
			String valuesLocation = String.format("'%s.%s'", dtoClass.getSimpleName(), field.getName());

			if (!field.getName().equals("serialVersionUID")) {

				boolean isAccessible = field.isAccessible();
				field.setAccessible(true);

				try {
					// Method getMethod = getMethodForField(field);
					// Object actualValue = getMethod.invoke(actual);
					// Object expectedValue = getMethod.invoke(expected);

					Object actualValue = field.get(actual);
					Object expectedValue = field.get(expected);

					assertDTOEquals(actualValue, expectedValue, valuesLocation);
				} catch (Exception e) {

					throw new IllegalStateException(String.format("Couldn't get %s values", valuesLocation), e);
				}
				field.setAccessible(isAccessible);
			}
		}
	}

	@SuppressWarnings("unused")
	private static Method getMethodForField(Field field) throws SecurityException, NoSuchMethodException {
		String fieldName = field.getName();
		Class<?> fieldType = field.getType();
		if (fieldType.equals(Boolean.class) || (fieldType.isPrimitive() && fieldType.getName().equals("boolean"))) {
			if (!fieldName.startsWith("is")) {
				fieldName = "is" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
			}
		} else {
			fieldName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		}
		return field.getDeclaringClass().getMethod(fieldName, new Class<?>[] {});
	}
}
